// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <nebula/types/fundamental.h>

namespace nebula {

    /// \brief Base class for all variable-size binary data types
    class TURBO_EXPORT BaseBinaryType : public DataType {
    public:
        using DataType::DataType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~BaseBinaryType() override = default;
    };

    /// \brief Concrete type class for variable-size binary data
    class TURBO_EXPORT BinaryType : public BaseBinaryType {
    public:
        static constexpr Type::type type_id = Type::BINARY;
        static constexpr bool is_utf8 = false;
        using offset_type = int32_t;
        using PhysicalType = BinaryType;

        static constexpr const char *type_name() { return "binary"; }

        BinaryType() : BinaryType(Type::BINARY) {}

        DataTypeLayout layout() const override {
            return DataTypeLayout({DataTypeLayout::Bitmap(),
                                   DataTypeLayout::FixedWidth(sizeof(offset_type)),
                                   DataTypeLayout::VariableWidth()});
        }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "binary"; }

    protected:
        std::string compute_fingerprint() const override;

        // Allow subclasses like StringType to change the logical type.
        explicit BinaryType(Type::type logical_type) : BaseBinaryType(logical_type) {}
    };



    /// \brief Concrete type class for variable-size binary view data
    class TURBO_EXPORT BinaryViewType : public DataType {
    public:
        static constexpr Type::type type_id = Type::BINARY_VIEW;
        static constexpr bool is_utf8 = false;
        using PhysicalType = BinaryViewType;

        static constexpr int kSize = 16;
        static constexpr int kInlineSize = 12;
        static constexpr int kPrefixSize = 4;

        /// Variable length string or binary with inline optimization for small values (12 bytes
        /// or fewer). This is similar to std::string_view except limited in size to INT32_MAX
        /// and at least the first four bytes of the string are copied inline (accessible
        /// without pointer dereference). This inline prefix allows failing comparisons early.
        /// Furthermore when dealing with short strings the CPU cache working set is reduced
        /// since many can be inline.
        ///
        /// This union supports two states:
        ///
        /// - Entirely inlined string data
        ///                |----|--------------|
        ///                 ^    ^
        ///                 |    |
        ///              size    in-line string data, zero padded
        ///
        /// - Reference into a buffer
        ///                |----|----|----|----|
        ///                 ^    ^    ^    ^
        ///                 |    |    |    |
        ///              size    |    |    `------.
        ///                  prefix   |           |
        ///                        buffer index   |
        ///                                  offset in buffer
        ///
        /// Adapted from TU Munich's UmbraDB [1], Velox, DuckDB.
        ///
        /// [1]: https://db.in.tum.de/~freitag/papers/p29-neumann-cidr20.pdf
        ///
        /// Alignment to 64 bits enables an aligned load of the size and prefix into
        /// a single 64 bit integer, which is useful to the comparison fast path.
        union alignas(int64_t) c_type {
            struct {
                int32_t size;
                std::array<uint8_t, kInlineSize> data;
            } inlined;

            struct {
                int32_t size;
                std::array<uint8_t, kPrefixSize> prefix;
                int32_t buffer_index;
                int32_t offset;
            } ref;

            /// The number of bytes viewed.
            int32_t size() const {
                // Size is in the common initial subsequence of each member of the union,
                // so accessing `inlined.size` is legal even if another member is active.
                return inlined.size;
            }

            /// True if the view's data is entirely stored inline.
            bool is_inline() const { return size() <= kInlineSize; }

            /// Return a pointer to the inline data of a view.
            ///
            /// For inline views, this points to the entire data of the view.
            /// For other views, this points to the 4 byte prefix.
            const uint8_t *inline_data() const &{
                // Since `ref.prefix` has the same address as `inlined.data`,
                // the branch will be trivially optimized out.
                return is_inline() ? inlined.data.data() : ref.prefix.data();
            }

            const uint8_t *inline_data() && = delete;
        };

        static_assert(sizeof(c_type) == kSize);
        static_assert(std::is_trivial_v<c_type>);

        static constexpr const char *type_name() { return "binary_view"; }

        BinaryViewType() : BinaryViewType(Type::BINARY_VIEW) {}

        DataTypeLayout layout() const override {
            return DataTypeLayout({DataTypeLayout::Bitmap(), DataTypeLayout::FixedWidth(kSize)},
                                  DataTypeLayout::VariableWidth());
        }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "binary_view"; }

    protected:
        std::string compute_fingerprint() const override;

        // Allow subclasses like StringType to change the logical type.
        explicit BinaryViewType(Type::type logical_type) : DataType(logical_type) {}
    };




    /// \brief Concrete type class for large variable-size binary data
    class TURBO_EXPORT LargeBinaryType : public BaseBinaryType {
    public:
        static constexpr Type::type type_id = Type::LARGE_BINARY;
        static constexpr bool is_utf8 = false;
        using offset_type = int64_t;
        using PhysicalType = LargeBinaryType;

        static constexpr const char *type_name() { return "large_binary"; }

        LargeBinaryType() : LargeBinaryType(Type::LARGE_BINARY) {}

        DataTypeLayout layout() const override {
            return DataTypeLayout({DataTypeLayout::Bitmap(),
                                   DataTypeLayout::FixedWidth(sizeof(offset_type)),
                                   DataTypeLayout::VariableWidth()});
        }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "large_binary"; }

    protected:
        std::string compute_fingerprint() const override;

        // Allow subclasses like LargeStringType to change the logical type.
        explicit LargeBinaryType(Type::type logical_type) : BaseBinaryType(logical_type) {}
    };


}  // namespace nebula
